// Copyright (c) 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "net/http/http_stream_factory_impl_job_controller.h"

#include "base/metrics/histogram_macros.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "net/base/host_mapping_rules.h"
#include "net/http/bidirectional_stream_impl.h"
#include "net/http/transport_security_state.h"
#include "net/spdy/spdy_session.h"
#include "net/tencent/network_settings.h"

namespace net {

HttpStreamFactoryImpl::JobController::JobController(
    HttpStreamFactoryImpl* factory,
    HttpStreamRequest::Delegate* delegate,
    HttpNetworkSession* session,
    JobFactory* job_factory)
    : factory_(factory)
    , session_(session)
    , job_factory_(job_factory)
    , request_(nullptr)
    , delegate_(delegate)
    , is_preconnect_(false)
    , job_bound_(false)
    ,
#if ENABLE_TENCENT(MULTI_REQUEST_JOB)
    bound_job_(nullptr)
    , proxy_impatient_job_(nullptr)
    , proxy_has_ever_faied_(false)
{
#else
    bound_job_(nullptr)
{
#endif
    DCHECK(factory);
}

HttpStreamFactoryImpl::JobController::~JobController()
{
    main_job_.reset();
    alternative_job_.reset();
#if ENABLE_TENCENT(MULTI_REQUEST_JOB)
    proxy_impatient_job_.reset();
#endif
    bound_job_ = nullptr;
}

bool HttpStreamFactoryImpl::JobController::for_websockets()
{
    return factory_->for_websockets_;
}

HttpStreamFactoryImpl::Request* HttpStreamFactoryImpl::JobController::Start(
    const HttpRequestInfo& request_info,
    HttpStreamRequest::Delegate* delegate,
    WebSocketHandshakeStreamBase::CreateHelper*
        websocket_handshake_stream_create_helper,
    const BoundNetLog& net_log,
    HttpStreamRequest::StreamType stream_type,
    RequestPriority priority,
    const SSLConfig& server_ssl_config,
    const SSLConfig& proxy_ssl_config)
{
    DCHECK(factory_);
    DCHECK(!request_);

    request_ = new Request(request_info.url, this, delegate,
        websocket_handshake_stream_create_helper, net_log,
        stream_type);

    CreateJobs(request_info, priority, server_ssl_config, proxy_ssl_config,
        delegate, stream_type, net_log);

    return request_;
}

void HttpStreamFactoryImpl::JobController::Preconnect(
    int num_streams,
    const HttpRequestInfo& request_info,
    const SSLConfig& server_ssl_config,
    const SSLConfig& proxy_ssl_config)
{
    DCHECK(!main_job_);
    DCHECK(!alternative_job_);

    is_preconnect_ = true;
    HostPortPair destination(HostPortPair::FromURL(request_info.url));
    GURL origin_url = ApplyHostMappingRules(request_info.url, &destination);

    const AlternativeService alternative_service = GetAlternativeServiceFor(
        request_info, nullptr, HttpStreamRequest::HTTP_STREAM);

    if (alternative_service.protocol != UNINITIALIZED_ALTERNATE_PROTOCOL) {
        if (session_->params().quic_disable_preconnect_if_0rtt && alternative_service.protocol == QUIC && session_->quic_stream_factory()->ZeroRTTEnabledFor(QuicServerId(alternative_service.host_port_pair(), request_info.privacy_mode))) {
            MaybeNotifyFactoryOfCompletion();
            return;
        }
        destination = alternative_service.host_port_pair();
        ignore_result(ApplyHostMappingRules(request_info.url, &destination));
    }

    // Due to how the socket pools handle priorities and idle sockets, only IDLE
    // priority currently makes sense for preconnects. The priority for
    // preconnects is currently ignored (see RequestSocketsForPool()), but could
    // be used at some point for proxy resolution or something.
    main_job_.reset(job_factory_->CreateJob(
        this, PRECONNECT, session_, request_info, IDLE, server_ssl_config,
        proxy_ssl_config, destination, origin_url, alternative_service,
        session_->net_log()));
    main_job_->Preconnect(num_streams);
}

LoadState HttpStreamFactoryImpl::JobController::GetLoadState() const
{
    DCHECK(request_);
#if ENABLE_TENCENT(MULTI_REQUEST_JOB)
    DCHECK(main_job_ || alternative_job_ || proxy_impatient_job_);
#else
    DCHECK(main_job_ || alternative_job_);
#endif
    if (bound_job_)
        return bound_job_->GetLoadState();

#if ENABLE_TENCENT(MULTI_REQUEST_JOB)
    if (!main_job_ && !alternative_job_ && proxy_impatient_job_)
        return proxy_impatient_job_->GetLoadState();
#endif

    // Just pick the first one.
    return main_job_ ? main_job_->GetLoadState()
                     : alternative_job_->GetLoadState();
}

void HttpStreamFactoryImpl::JobController::OnRequestComplete()
{
    CancelJobs();
    DCHECK(request_);
    request_ = nullptr;
    if (bound_job_) {
        if (bound_job_->job_type() == MAIN) {
            main_job_.reset();
#if ENABLE_TENCENT(MULTI_REQUEST_JOB)
        } else if (bound_job_->job_type() == IMPATIENT_DIRECT) {
            proxy_impatient_job_.reset();
#endif
        } else {
            DCHECK(bound_job_->job_type() == ALTERNATIVE);
            alternative_job_.reset();
        }
        bound_job_ = nullptr;
    }
    MaybeNotifyFactoryOfCompletion();
}

int HttpStreamFactoryImpl::JobController::RestartTunnelWithProxyAuth(
    const AuthCredentials& credentials)
{
    DCHECK(bound_job_);
    return bound_job_->RestartTunnelWithProxyAuth(credentials);
}

void HttpStreamFactoryImpl::JobController::SetPriority(
    RequestPriority priority)
{
    if (main_job_) {
        main_job_->SetPriority(priority);
    }
    if (alternative_job_) {
        alternative_job_->SetPriority(priority);
    }
#if ENABLE_TENCENT(MULTI_REQUEST_JOB)
    if (proxy_impatient_job_) {
        proxy_impatient_job_->SetPriority(priority);
    }
#endif
}

void HttpStreamFactoryImpl::JobController::OnStreamReady(
    Job* job,
    const SSLConfig& used_ssl_config,
    const ProxyInfo& used_proxy_info)
{
    DCHECK(job);

    if (job_bound_ && bound_job_ != job) {
        // We have bound a job to the associated Request, |job| has been orphaned.
        OnOrphanedJobComplete(job);
        return;
    }
    std::unique_ptr<HttpStream> stream = job->ReleaseStream();
    DCHECK(stream);

    MarkRequestComplete(job->was_npn_negotiated(), job->protocol_negotiated(),
        job->using_spdy());

    if (!request_)
        return;
    DCHECK(!factory_->for_websockets_);
    DCHECK_EQ(HttpStreamRequest::HTTP_STREAM, request_->stream_type());
    OnJobSucceeded(job);
    request_->OnStreamReady(used_ssl_config, used_proxy_info, stream.release());
}

void HttpStreamFactoryImpl::JobController::OnBidirectionalStreamImplReady(
    Job* job,
    const SSLConfig& used_ssl_config,
    const ProxyInfo& used_proxy_info)
{
    DCHECK(job);

    if (job_bound_ && bound_job_ != job) {
        // We have bound a job to the associated Request, |job| has been orphaned.
        OnOrphanedJobComplete(job);
        return;
    }

    MarkRequestComplete(job->was_npn_negotiated(), job->protocol_negotiated(),
        job->using_spdy());

    if (!request_)
        return;
    std::unique_ptr<BidirectionalStreamImpl> stream = job->ReleaseBidirectionalStream();
    DCHECK(stream);
    DCHECK(!factory_->for_websockets_);
    DCHECK_EQ(HttpStreamRequest::BIDIRECTIONAL_STREAM, request_->stream_type());

    OnJobSucceeded(job);
    request_->OnBidirectionalStreamImplReady(used_ssl_config, used_proxy_info,
        stream.release());
}

void HttpStreamFactoryImpl::JobController::OnWebSocketHandshakeStreamReady(
    Job* job,
    const SSLConfig& used_ssl_config,
    const ProxyInfo& used_proxy_info,
    WebSocketHandshakeStreamBase* stream)
{
    DCHECK(job);

    MarkRequestComplete(job->was_npn_negotiated(), job->protocol_negotiated(),
        job->using_spdy());

    if (!request_)
        return;
    DCHECK(factory_->for_websockets_);
    DCHECK_EQ(HttpStreamRequest::HTTP_STREAM, request_->stream_type());
    DCHECK(stream);

    OnJobSucceeded(job);
    request_->OnWebSocketHandshakeStreamReady(used_ssl_config, used_proxy_info,
        stream);
}

void HttpStreamFactoryImpl::JobController::OnStreamFailed(
    Job* job,
    int status,
    const SSLConfig& used_ssl_config)
{
    NET_LOG_STREAM("HttpStreamFactoryImpl::JobController::OnStreamFailed job-type:%d status:%d", job->job_type(), status);
    if (job_bound_ && bound_job_ != job) {
        // We have bound a job to the associated Request, |job| has been orphaned.
        OnOrphanedJobComplete(job);
        return;
    }

    if (!request_)
        return;
    DCHECK_NE(OK, status);
    DCHECK(job);

    if (!bound_job_) {
        if (main_job_ && alternative_job_) {
            // Hey, we've got other jobs! Maybe one of them will succeed, let's just
            // ignore this failure.
            factory_->request_map_.erase(job);
            // Notify all the other jobs that this one failed.
            if (job->job_type() == MAIN) {
                alternative_job_->MarkOtherJobComplete(*job);
                main_job_.reset();
#if ENABLE_TENCENT(MULTI_REQUEST_JOB)
                // 有一种情况，proxy请求先行，直连请求后行。直连请求成功前proxy-job已经出错并在这里析构，这时候要做上proxy出错标记
                //  具体用处见proxy_has_ever_faied_的使用处
                if (job->proxy_info().is_spdy()) {
                    NET_LOG_STREAM("  spdy proxy has ever failed...");
                    proxy_has_ever_faied_ = true;
                }
#endif
            } else {
                DCHECK(job->job_type() == ALTERNATIVE);
                main_job_->MarkOtherJobComplete(*job);
                alternative_job_.reset();
            }
            return;
        } else {
            BindJob(job);
        }
    }

    request_->OnStreamFailed(status, used_ssl_config);
}

#if ENABLE_TENCENT(MULTI_REQUEST_JOB)
void HttpStreamFactoryImpl::JobController::StartProxyImpatientTimer()
{
    if (proxy_impatient_timer_.IsRunning())
        return;

    NET_LOG_STREAM(" HttpStreamFactoryImpl::JobController::startProxyImpatientTimer");
    proxy_impatient_timer_.Start(
        FROM_HERE,
        base::TimeDelta::FromMilliseconds(NetworkSettings::GetProxyImpatientTimerDuration()),
        base::Bind(&HttpStreamFactoryImpl::JobController::OnProxyImpaticentTimerRaged, base::Unretained(this)));
}

void HttpStreamFactoryImpl::JobController::StopProxyImpatientTimer()
{
    if (!proxy_impatient_timer_.IsRunning())
        return;

    NET_LOG_STREAM(" HttpStreamFactoryImpl::JobController::stopProxyImpatientTimer");
    proxy_impatient_timer_.Stop();
}

void HttpStreamFactoryImpl::JobController::OnProxyImpaticentTimerRaged()
{
    if (proxy_impatient_job_.get() == NULL)
        return;
    NET_LOG_STREAM(" HttpStreamFactoryImpl::JobController::onProxyImpaticentTimerRaged");

    AttachJob(proxy_impatient_job_.get());
    // NOTICE：这里必须release掉，把指针的生命周期交给Request的jobs_去处理！
    proxy_impatient_job_->Start(request_->stream_type());
}
#endif

void HttpStreamFactoryImpl::JobController::OnCertificateError(
    Job* job,
    int status,
    const SSLConfig& used_ssl_config,
    const SSLInfo& ssl_info)
{
    if (job_bound_ && bound_job_ != job) {
        // We have bound a job to the associated Request, |job| has been orphaned.
        OnOrphanedJobComplete(job);
        return;
    }

    if (!request_)
        return;
    DCHECK_NE(OK, status);
    if (!bound_job_)
        BindJob(job);

    request_->OnCertificateError(status, used_ssl_config, ssl_info);
}

void HttpStreamFactoryImpl::JobController::OnHttpsProxyTunnelResponse(
    Job* job,
    const HttpResponseInfo& response_info,
    const SSLConfig& used_ssl_config,
    const ProxyInfo& used_proxy_info,
    HttpStream* stream)
{
    if (job_bound_ && bound_job_ != job) {
        // We have bound a job to the associated Request, |job| has been orphaned.
        OnOrphanedJobComplete(job);
        return;
    }

    if (!bound_job_)
        BindJob(job);
    if (!request_)
        return;
    request_->OnHttpsProxyTunnelResponse(response_info, used_ssl_config,
        used_proxy_info, stream);
}

void HttpStreamFactoryImpl::JobController::OnNeedsClientAuth(
    Job* job,
    const SSLConfig& used_ssl_config,
    SSLCertRequestInfo* cert_info)
{
    if (job_bound_ && bound_job_ != job) {
        // We have bound a job to the associated Request, |job| has been orphaned.
        OnOrphanedJobComplete(job);
        return;
    }
    if (!request_)
        return;
    if (!bound_job_)
        BindJob(job);

    request_->OnNeedsClientAuth(used_ssl_config, cert_info);
}

void HttpStreamFactoryImpl::JobController::OnNeedsProxyAuth(
    Job* job,
    const HttpResponseInfo& proxy_response,
    const SSLConfig& used_ssl_config,
    const ProxyInfo& used_proxy_info,
    HttpAuthController* auth_controller)
{
    if (job_bound_ && bound_job_ != job) {
        // We have bound a job to the associated Request, |job| has been orphaned.
        OnOrphanedJobComplete(job);
        return;
    }

    if (!request_)
        return;
    if (!bound_job_)
        BindJob(job);
    request_->OnNeedsProxyAuth(proxy_response, used_ssl_config, used_proxy_info,
        auth_controller);
}

void HttpStreamFactoryImpl::JobController::OnNewSpdySessionReady(
    Job* job,
    const base::WeakPtr<SpdySession>& spdy_session,
    bool direct)
{
    DCHECK(job);
    DCHECK(job->using_spdy());

    bool is_job_orphaned = job_bound_ && bound_job_ != job;

    // Cache these values in case the job gets deleted.
    const SSLConfig used_ssl_config = job->server_ssl_config();
    const ProxyInfo used_proxy_info = job->proxy_info();
    const bool was_npn_negotiated = job->was_npn_negotiated();
    const NextProto protocol_negotiated = job->protocol_negotiated();
    const bool using_spdy = job->using_spdy();
    const BoundNetLog net_log = job->net_log();

    // Cache this so we can still use it if the JobController is deleted.
    HttpStreamFactoryImpl* factory = factory_;

    // Notify |request_|.
    if (!is_preconnect_ && !is_job_orphaned) {
        DCHECK(request_);

        // The first case is the usual case.
        if (!job_bound_) {
            BindJob(job);
        }

        MarkRequestComplete(was_npn_negotiated, protocol_negotiated, using_spdy);

        std::unique_ptr<HttpStream> stream;
        std::unique_ptr<BidirectionalStreamImpl> bidirectional_stream_impl;

        if (for_websockets()) {
            // TODO(ricea): Re-instate this code when WebSockets over SPDY is
            // implemented.
            NOTREACHED();
        } else if (job->stream_type() == HttpStreamRequest::BIDIRECTIONAL_STREAM) {
            bidirectional_stream_impl = job->ReleaseBidirectionalStream();
            DCHECK(bidirectional_stream_impl);
            delegate_->OnBidirectionalStreamImplReady(
                used_ssl_config, used_proxy_info,
                bidirectional_stream_impl.release());
        } else {
            stream = job->ReleaseStream();
            DCHECK(stream);
            delegate_->OnStreamReady(used_ssl_config, used_proxy_info,
                stream.release());
        }
    }

    // Notify |factory_|. |request_| and |bounded_job_| might be deleted already.
    if (spdy_session && spdy_session->IsAvailable()) {
        factory->OnNewSpdySessionReady(spdy_session, direct, used_ssl_config,
            used_proxy_info, was_npn_negotiated,
            protocol_negotiated, using_spdy, net_log);
    }
    if (is_job_orphaned) {
        OnOrphanedJobComplete(job);
    }
}

void HttpStreamFactoryImpl::JobController::OnPreconnectsComplete(Job* job)
{
    DCHECK_EQ(main_job_.get(), job);
    main_job_.reset();
    factory_->OnPreconnectsCompleteInternal();
    MaybeNotifyFactoryOfCompletion();
}

void HttpStreamFactoryImpl::JobController::OnOrphanedJobComplete(
    const Job* job)
{
    NET_LOG_STREAM("HttpStreamFactoryImpl::JobController::OnOrphanedJobComplete  job-type:%d", job->job_type());
    if (job->job_type() == MAIN) {
        DCHECK_EQ(main_job_.get(), job);
        main_job_.reset();
#if ENABLE_TENCENT(MULTI_REQUEST_JOB)
    } else if (job->job_type() == IMPATIENT_DIRECT) {
        DCHECK_EQ(proxy_impatient_job_.get(), job);
        proxy_impatient_job_.reset();
#endif
    } else {
        DCHECK_EQ(alternative_job_.get(), job);
        alternative_job_.reset();
    }

    MaybeNotifyFactoryOfCompletion();
}

void HttpStreamFactoryImpl::JobController::AddConnectionAttemptsToRequest(
    Job* job,
    const ConnectionAttempts& attempts)
{
    if (is_preconnect_ || (job_bound_ && bound_job_ != job))
        return;

    DCHECK(request_);
    request_->AddConnectionAttempts(attempts);
}

void HttpStreamFactoryImpl::JobController::SetSpdySessionKey(
    Job* job,
    const SpdySessionKey& spdy_session_key)
{
    if (is_preconnect_ || (job_bound_ && bound_job_ != job))
        return;

    DCHECK(request_);
    if (!request_->HasSpdySessionKey()) {
        RequestSet& request_set = factory_->spdy_session_request_map_[spdy_session_key];
        DCHECK(!ContainsKey(request_set, request_));
        request_set.insert(request_);
        request_->SetSpdySessionKey(spdy_session_key);
    }
}

void HttpStreamFactoryImpl::JobController::
    RemoveRequestFromSpdySessionRequestMapForJob(Job* job)
{
    if (is_preconnect_ || (job_bound_ && bound_job_ != job))
        return;
    DCHECK(request_);

    RemoveRequestFromSpdySessionRequestMap();
}

void HttpStreamFactoryImpl::JobController::
    RemoveRequestFromSpdySessionRequestMap()
{
    const SpdySessionKey* spdy_session_key = request_->spdy_session_key();
    if (spdy_session_key) {
        SpdySessionRequestMap& spdy_session_request_map = factory_->spdy_session_request_map_;
        DCHECK(ContainsKey(spdy_session_request_map, *spdy_session_key));
        RequestSet& request_set = spdy_session_request_map[*spdy_session_key];
        DCHECK(ContainsKey(request_set, request_));
        request_set.erase(request_);
        if (request_set.empty())
            spdy_session_request_map.erase(*spdy_session_key);
        request_->ResetSpdySessionKey();
    }
}

const BoundNetLog* HttpStreamFactoryImpl::JobController::GetNetLog(
    Job* job) const
{
    if (is_preconnect_ || (job_bound_ && bound_job_ != job))
        return nullptr;
    DCHECK(request_);
    return &request_->net_log();
}

WebSocketHandshakeStreamBase::CreateHelper* HttpStreamFactoryImpl::
    JobController::websocket_handshake_stream_create_helper()
{
    DCHECK(request_);
    return request_->websocket_handshake_stream_create_helper();
}

void HttpStreamFactoryImpl::JobController::CreateJobs(
    const HttpRequestInfo& request_info,
    RequestPriority priority,
    const SSLConfig& server_ssl_config,
    const SSLConfig& proxy_ssl_config,
    HttpStreamRequest::Delegate* delegate,
    HttpStreamRequest::StreamType stream_type,
    const BoundNetLog& net_log)
{
    DCHECK(!main_job_);
    DCHECK(!alternative_job_);
#if ENABLE_TENCENT(MULTI_REQUEST_JOB)
    DCHECK(!proxy_impatient_job_);
#endif
    HostPortPair destination(HostPortPair::FromURL(request_info.url));
    GURL origin_url = ApplyHostMappingRules(request_info.url, &destination);

    main_job_.reset(job_factory_->CreateJob(
        this, MAIN, session_, request_info, priority, server_ssl_config,
        proxy_ssl_config, destination, origin_url, net_log.net_log()));
    AttachJob(main_job_.get());

    // Create an alternative job if alternative service is set up for this domain.
    const AlternativeService alternative_service = GetAlternativeServiceFor(request_info, delegate, stream_type);

    if (alternative_service.protocol != UNINITIALIZED_ALTERNATE_PROTOCOL) {
        // Never share connection with other jobs for FTP requests.
        DVLOG(1) << "Selected alternative service (host: "
                 << alternative_service.host_port_pair().host()
                 << " port: " << alternative_service.host_port_pair().port() << ")";

        DCHECK(!request_info.url.SchemeIs("ftp"));
        HostPortPair alternative_destination(alternative_service.host_port_pair());
        ignore_result(
            ApplyHostMappingRules(request_info.url, &alternative_destination));

        alternative_job_.reset(job_factory_->CreateJob(
            this, ALTERNATIVE, session_, request_info, priority, server_ssl_config,
            proxy_ssl_config, alternative_destination, origin_url,
            alternative_service, net_log.net_log()));
        AttachJob(alternative_job_.get());

        main_job_->WaitFor(alternative_job_.get());
        // Make sure to wait until we call WaitFor(), before starting
        // |alternative_job|, otherwise |alternative_job| will not notify |job|
        // appropriately.
        alternative_job_->Start(request_->stream_type());
    }
#if ENABLE_TENCENT(MULTI_REQUEST_JOB)
    else {
        // 同时给request储备一个性格暴虐的Job，它的忍耐力通常不太好，几秒钟如果reqeust还没有连接响应，它就要被触动。
        //   这个Job只能直连，它的触发一般处在以下场景：
        //    1、主资源请求；
        //    2、http或者https，不区分直连代理；
        //    3、主job请求如果几秒钟过去还没有响应成功（注意，这个job做的事情只是[代理判定][socket连接]），则暴虐Job被发起，谁先成功用谁；
        NET_LOG_STREAM(" Create impatience job.");
        HttpRequestInfo impatience_reqeust_info = request_info;
        impatience_reqeust_info.load_flags |= LOAD_BYPASS_PROXY;
        proxy_impatient_job_.reset(job_factory_->CreateJob(
            this, IMPATIENT_DIRECT, session_, impatience_reqeust_info, priority, server_ssl_config,
            proxy_ssl_config, destination, origin_url, net_log.net_log()));
        // do not start here.
    }
#endif
    // Even if |alternative_job| has already finished, it will not have notified
    // the request yet, since we defer that to the next iteration of the
    // MessageLoop, so starting |main_job_| is always safe.
    main_job_->Start(request_->stream_type());
#if ENABLE_TENCENT(MULTI_REQUEST_JOB)
    // 开始挑战Job的忍耐...
    //    主资源才做；http和https才做。目前不去分代理直连，全都发起。
    //    直连的话也会出问题，如果规定时间内还没有请求成功，则直接再做一次请求。
    if (NetworkSettings::IsProxyImpatientJobEnabled()
        && request_info.resource_type == HttpRequestInfo::MAIN_FRAME
        && request_info.url.SchemeIsHTTPOrHTTPS()
        /*&& priority > LOW*/)
        StartProxyImpatientTimer();
#endif
}

void HttpStreamFactoryImpl::JobController::AttachJob(Job* job)
{
    DCHECK(job);
    factory_->request_map_[job] = request_;
}

void HttpStreamFactoryImpl::JobController::BindJob(Job* job)
{
    DCHECK(request_);
    DCHECK(job);
#if ENABLE_TENCENT(MULTI_REQUEST_JOB)
    DCHECK(job == alternative_job_.get() || job == main_job_.get() || job == proxy_impatient_job_.get());
#else
  DCHECK(job == alternative_job_.get() || job == main_job_.get();
#endif
    DCHECK(!job_bound_);
    DCHECK(!bound_job_);
    NET_LOG_STREAM("HttpStreamFactoryImpl::JobController::BindJob job:%d type:%d", (int)job, job->job_type());
    job_bound_ = true;
    bound_job_ = job;
    factory_->request_map_.erase(job);

    request_->net_log().AddEvent(
        NetLog::TYPE_HTTP_STREAM_REQUEST_BOUND_TO_JOB,
        job->net_log().source().ToEventParametersCallback());
    job->net_log().AddEvent(
        NetLog::TYPE_HTTP_STREAM_JOB_BOUND_TO_REQUEST,
        request_->net_log().source().ToEventParametersCallback());

    OrphanUnboundJob();
}

void HttpStreamFactoryImpl::JobController::CancelJobs()
{
    DCHECK(request_);
    RemoveRequestFromSpdySessionRequestMap();
    if (job_bound_)
        return;
    if (alternative_job_) {
        factory_->request_map_.erase(alternative_job_.get());
        alternative_job_.reset();
    }
    if (main_job_) {
        factory_->request_map_.erase(main_job_.get());
        main_job_.reset();
    }
#if ENABLE_TENCENT(MULTI_REQUEST_JOB)
    if (proxy_impatient_job_) {
        factory_->request_map_.erase(proxy_impatient_job_.get());
        proxy_impatient_job_.reset();
    }
#endif
}

void HttpStreamFactoryImpl::JobController::OrphanUnboundJob()
{
    DCHECK(request_);
    RemoveRequestFromSpdySessionRequestMap();

    DCHECK(bound_job_);
    if (bound_job_->job_type() == MAIN && alternative_job_) {
        factory_->request_map_.erase(alternative_job_.get());
        alternative_job_->Orphan();
    } else if (bound_job_->job_type() == ALTERNATIVE && main_job_) {
        // Orphan main job.
        factory_->request_map_.erase(main_job_.get());
        main_job_->Orphan();
    }

#if ENABLE_TENCENT(MULTI_REQUEST_JOB)
    //
    if (bound_job_->job_type() == MAIN && proxy_impatient_job_) {
        factory_->request_map_.erase(proxy_impatient_job_.get());
        proxy_impatient_job_->Orphan();
    } else if (bound_job_->job_type() == IMPATIENT_DIRECT && main_job_) {
        factory_->request_map_.erase(main_job_.get());
        main_job_->Orphan();
    }
#endif
}

void HttpStreamFactoryImpl::JobController::OnJobSucceeded(Job* job)
{
    // |job| should only be nullptr if we're being serviced by a late bound
    // SpdySession (one that was not created by a job in our |jobs_| set).

    NET_LOG_STREAM("HttpStreamFactoryImpl::JobController::OnJobSucceeded job:%d", (int)job);
    if (!job) {
        DCHECK(!bound_job_);
        // NOTE(willchan): We do *NOT* call OrphanUnboundJob() here. The reason is
        // because we *WANT* to cancel the unnecessary Jobs from other requests if
        // another Job completes first.
        // TODO(mbelshe): Revisit this when we implement ip connection pooling of
        // SpdySessions. Do we want to orphan the jobs for a different hostname so
        // they complete? Or do we want to prevent connecting a new SpdySession if
        // we've already got one available for a different hostname where the ip
        // address matches up?
        CancelJobs();
        return;
    }
    if (!bound_job_) {
        if (main_job_ && alternative_job_) {
            job->ReportJobSucceededForRequest();
            // Notify all the other jobs that this one succeeded.
            if (job->job_type() == MAIN) {
                alternative_job_->MarkOtherJobComplete(*job);
            } else {
                DCHECK(job->job_type() == ALTERNATIVE);
                main_job_->MarkOtherJobComplete(*job);
            }
        }

#if ENABLE_TENCENT(MULTI_REQUEST_JOB)
        StopProxyImpatientTimer();
        // 如果有alternative_job_则不创建proxy_impatient_job_
        if (main_job_ && main_job_->proxy_info().is_spdy() && proxy_impatient_job_) {
            // TODO(Coomy): 其他的上报；
            // 上报灯塔；通知走直连了 清理缓存？
            NET_LOG_STREAM("  job-type:%d", job->job_type());
            if (job->job_type() == IMPATIENT_DIRECT)
                main_job_->MarkOtherJobComplete(*job);
            else if (job->job_type() == MAIN)
                proxy_impatient_job_->MarkOtherJobComplete(*job);
        }
#endif
        BindJob(job);
        return;
    }
    DCHECK(bound_job_);
}

void HttpStreamFactoryImpl::JobController::MarkRequestComplete(
    bool was_npn_negotiated,
    NextProto protocol_negotiated,
    bool using_spdy)
{
    if (request_)
        request_->Complete(was_npn_negotiated, protocol_negotiated, using_spdy);
}

void HttpStreamFactoryImpl::JobController::MaybeNotifyFactoryOfCompletion()
{
    if (!request_ && !main_job_ && !alternative_job_) {
        DCHECK(!bound_job_);
        factory_->OnJobControllerComplete(this);
    }
}

GURL HttpStreamFactoryImpl::JobController::ApplyHostMappingRules(
    const GURL& url,
    HostPortPair* endpoint)
{
    const HostMappingRules* mapping_rules = session_->params().host_mapping_rules;
    if (mapping_rules && mapping_rules->RewriteHost(endpoint)) {
        url::Replacements<char> replacements;
        const std::string port_str = base::UintToString(endpoint->port());
        replacements.SetPort(port_str.c_str(), url::Component(0, port_str.size()));
        replacements.SetHost(endpoint->host().c_str(),
            url::Component(0, endpoint->host().size()));
        return url.ReplaceComponents(replacements);
    }
    return url;
}

bool HttpStreamFactoryImpl::JobController::IsQuicWhitelistedForHost(
    const std::string& host)
{
    bool whitelist_needed = false;
    for (QuicVersion version : session_->params().quic_supported_versions) {
        if (version <= QUIC_VERSION_30) {
            whitelist_needed = true;
            break;
        }
    }

    // The QUIC whitelist is not needed in QUIC versions after 30.
    if (!whitelist_needed)
        return true;

    if (session_->params().transport_security_state->IsGooglePinnedHost(host))
        return true;

    return ContainsKey(session_->params().quic_host_whitelist,
        base::ToLowerASCII(host));
}

AlternativeService
HttpStreamFactoryImpl::JobController::GetAlternativeServiceFor(
    const HttpRequestInfo& request_info,
    HttpStreamRequest::Delegate* delegate,
    HttpStreamRequest::StreamType stream_type)
{
    AlternativeService alternative_service = GetAlternativeServiceForInternal(request_info, delegate, stream_type);
    AlternativeServiceType type;
    if (alternative_service.protocol == UNINITIALIZED_ALTERNATE_PROTOCOL) {
        type = NO_ALTERNATIVE_SERVICE;
    } else if (alternative_service.protocol == QUIC) {
        if (request_info.url.host() == alternative_service.host) {
            type = QUIC_SAME_DESTINATION;
        } else {
            type = QUIC_DIFFERENT_DESTINATION;
        }
    } else {
        if (request_info.url.host() == alternative_service.host) {
            type = NOT_QUIC_SAME_DESTINATION;
        } else {
            type = NOT_QUIC_DIFFERENT_DESTINATION;
        }
    }
    UMA_HISTOGRAM_ENUMERATION("Net.AlternativeServiceTypeForRequest", type,
        MAX_ALTERNATIVE_SERVICE_TYPE);
    return alternative_service;
}

AlternativeService
HttpStreamFactoryImpl::JobController::GetAlternativeServiceForInternal(
    const HttpRequestInfo& request_info,
    HttpStreamRequest::Delegate* delegate,
    HttpStreamRequest::StreamType stream_type)
{
    GURL original_url = request_info.url;

    if (!original_url.SchemeIs("https"))
        return AlternativeService();

    url::SchemeHostPort origin(original_url);
    HttpServerProperties& http_server_properties = *session_->http_server_properties();
    const AlternativeServiceVector alternative_service_vector = http_server_properties.GetAlternativeServices(origin);
    if (alternative_service_vector.empty())
        return AlternativeService();

    bool quic_advertised = false;
    bool quic_all_broken = true;

    // First Alt-Svc that is not marked as broken.
    AlternativeService first_alternative_service;

    for (const AlternativeService& alternative_service :
        alternative_service_vector) {
        DCHECK(IsAlternateProtocolValid(alternative_service.protocol));
        if (!quic_advertised && alternative_service.protocol == QUIC)
            quic_advertised = true;
        if (http_server_properties.IsAlternativeServiceBroken(
                alternative_service)) {
            HistogramAlternateProtocolUsage(ALTERNATE_PROTOCOL_USAGE_BROKEN);
            continue;
        }

        // Some shared unix systems may have user home directories (like
        // http://foo.com/~mike) which allow users to emit headers.  This is a bad
        // idea already, but with Alternate-Protocol, it provides the ability for a
        // single user on a multi-user system to hijack the alternate protocol.
        // These systems also enforce ports <1024 as restricted ports.  So don't
        // allow protocol upgrades to user-controllable ports.
        const int kUnrestrictedPort = 1024;
        if (!session_->params().enable_user_alternate_protocol_ports && (alternative_service.port >= kUnrestrictedPort && origin.port() < kUnrestrictedPort))
            continue;

        if (alternative_service.protocol >= NPN_SPDY_MINIMUM_VERSION && alternative_service.protocol <= NPN_SPDY_MAXIMUM_VERSION) {
            if (alternative_service.protocol == NPN_SPDY_3_1 && !HttpStreamFactory::spdy_enabled())
                continue;

            if (origin.host() != alternative_service.host && !session_->params().enable_http2_alternative_service_with_different_host) {
                continue;
            }

            // Cache this entry if we don't have a non-broken Alt-Svc yet.
            if (first_alternative_service.protocol == UNINITIALIZED_ALTERNATE_PROTOCOL)
                first_alternative_service = alternative_service;
            continue;
        }

        DCHECK_EQ(QUIC, alternative_service.protocol);
        if (origin.host() != alternative_service.host && !session_->params().enable_quic_alternative_service_with_different_host) {
            continue;
        }

        quic_all_broken = false;
        if (!session_->params().enable_quic)
            continue;

        if (!IsQuicWhitelistedForHost(origin.host()))
            continue;

        if (stream_type == HttpStreamRequest::BIDIRECTIONAL_STREAM && session_->params().quic_disable_bidirectional_streams) {
            continue;
        }

        if (session_->quic_stream_factory()->IsQuicDisabled(
                alternative_service.port))
            continue;

        if (!original_url.SchemeIs("https"))
            continue;

        // Check whether there is an existing QUIC session to use for this origin.
        HostPortPair mapped_origin(origin.host(), origin.port());
        ignore_result(ApplyHostMappingRules(original_url, &mapped_origin));
        QuicServerId server_id(mapped_origin, request_info.privacy_mode);

        HostPortPair destination(alternative_service.host_port_pair());
        ignore_result(ApplyHostMappingRules(original_url, &destination));

        if (session_->quic_stream_factory()->CanUseExistingSession(server_id,
                destination)) {
            return alternative_service;
        }

        // Cache this entry if we don't have a non-broken Alt-Svc yet.
        if (first_alternative_service.protocol == UNINITIALIZED_ALTERNATE_PROTOCOL)
            first_alternative_service = alternative_service;
    }

    // Ask delegate to mark QUIC as broken for the origin.
    if (quic_advertised && quic_all_broken && delegate != nullptr)
        delegate->OnQuicBroken();

    return first_alternative_service;
}
}
